package com.mark.search.config;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mark.search.StrUtil;
import com.mark.search.dto.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * @author : mark
 * @desc : es工具类
 * @since : 2022/7/08 16:45
 */
@Slf4j
@Component
public class ElasticSearchOperation {

    @Resource
    EsConfig esConfig;

    private final RestHighLevelClient restHighLevelClient;

    public ElasticSearchOperation(RestHighLevelClient restHighLevelClient) {
        this.restHighLevelClient = restHighLevelClient;
    }

    /**
     * 新增数据到es
     *
     * @param indexName 索引名称
     * @param data      对象
     * @param indexId   索引id
     */
    public boolean add(String indexName, Object data, String indexId) {
        if (indexName == null || data == null || indexId == null) {
            return false;
        }
        IndexRequest request = new IndexRequest(indexName);
        request.id(indexId);
        request.source(JSONObject.toJSONString(data), XContentType.JSON);
        try {
            IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
            if (indexResponse == null) {
                return false;
            }
            log.info("写入es成功，风控订单号:{}", indexId);
            return (indexResponse.getResult() == DocWriteResponse.Result.CREATED
                    || indexResponse.getResult() == DocWriteResponse.Result.UPDATED);
        } catch (Throwable e) {
            log.error(
                    "ElasticSearchGateway#add's params  indexName={} and data={} and indexId = {} and Exception Occurred:",
                    indexName, data, indexId, e);
        }
        return false;
    }


    /**
     * <p>
     * 更新数据,继承这个对象就行
     * </p>
     */
    public Boolean update(EsDTO esDTO) {
        UpdateRequest updateRequest = new UpdateRequest(esConfig.getIndexName1(), esDTO.getId());
        updateRequest.retryOnConflict(3);
        updateRequest.doc(JSON.toJSONString(esDTO), XContentType.JSON);
        UpdateResponse updateResponse = null;
        try {
            updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("es update fail,params:{}", JSON.toJSONString(esDTO), e);
        }
        return updateResponse != null && updateResponse.status().getStatus() == 200;
    }


    /**
     * 业务查询
     *
     * @param entity 参数
     * @return 分页查询结果
     */
    public RespVO queryOrders(QueryParam entity) {
        RespVO vo = new RespVO();
        try {
            Map<String, Object> map = search(entity);
            List<EsExampleResp> voList = (List<EsExampleResp>) map.get("list");
            Long total = (Long) map.get("total");
            vo.setVoList(voList);
            vo.setTotal(total);
        } catch (Exception e) {
            log.error("query Exception,param:{}exception:{}", JSON.toJSONString(entity), e);
        }
        return vo;
    }

    /**
     * 查询es,适合订单搜索等
     */
    public Map<String, Object> search(QueryParam entity) throws IOException {
        BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("*"); //所有的字段都高亮
        highlightBuilder.requireFieldMatch(false);//如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<font color='red'>").postTags("</font'>")
                //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等;  最大高亮分片数
                .fragmentSize(800000)
                .numOfFragments(0);//从第一个分片获取高亮片段
        sourceBuilder.highlighter(highlightBuilder);

        String status = entity.getStatus();
        if (StrUtil.isNotEmpty(status)) {
            //in查询，已经有结果了
            if (String.valueOf(OrderStateVOEnum.审核结束.getCode()).equals(status)) {
                TermsQueryBuilder termsQuery = QueryBuilders.termsQuery("status",
                        OrderStateVOEnum.审核通过.getCode(), String.valueOf(
                                OrderStateVOEnum.审核拒绝.getCode()));
                boolBuilder.must(termsQuery);
            }
        }

        //多选查询
        if (StrUtil.isNotEmpty(entity.getName())) {
            List<String> nameList = Arrays.asList(entity.getName().split(","));
            TermsQueryBuilder termsQuery = QueryBuilders.termsQuery("name", nameList);
            boolBuilder.must(termsQuery);
        }
        //精准搜索
        if (StrUtil.isNotEmpty(entity.getId())) {
            MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("id", entity.getId().trim());
            boolBuilder.must(matchQueryBuilder);
        }
        //模糊搜索
        if (StrUtil.isNotEmpty(entity.getDesc())) {
            WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("desc", "*" + entity.getDesc() + "*");
            boolBuilder.must(wildcardQueryBuilder);
        }
        //时间范围搜索
        if (StrUtil.isNotEmpty(entity.getStartTime()) && StrUtil.isNotEmpty(entity.getEndTime())) {
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("startTime");
            rangeQueryBuilder.gte(entity.getStartTime());
            rangeQueryBuilder.lte(entity.getEndTime());
            boolBuilder.must(rangeQueryBuilder);
        }


        sourceBuilder.query(boolBuilder);
        //分页查询
        sourceBuilder.from((entity.getPageNum() - 1) * entity.getPageSize());
        sourceBuilder.size(entity.getPageSize());
        sourceBuilder.timeout(new TimeValue(20, TimeUnit.SECONDS));
        String[] fields = {"id", "name", "status", "desc", "startTime", "endTime"};
        sourceBuilder.fetchSource(fields, new String[]{});
        SearchRequest searchRequest = new SearchRequest(esConfig.getIndexName1());
        //按照更新时间排序
        FieldSortBuilder fsb = SortBuilders.fieldSort("startTime");
        fsb.order(SortOrder.DESC);
        sourceBuilder.sort(fsb);
        sourceBuilder.query(boolBuilder);
        sourceBuilder.trackTotalHits(true);
        searchRequest.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        List<Map<String, Object>> result = setSearchResponse(response);
        List<EsExampleResp> list = new ArrayList<>();
        for (Map<String, Object> stringObjectMap : result) {
            EsExampleResp vo = MapToBean(stringObjectMap, EsExampleResp.class);
            list.add(vo);
        }
        Map<String, Object> map = new HashMap<>(4);
        map.put("list", list);
        map.put("total", response.getHits().getTotalHits().value);
        return map;
    }


    /**
     * 查询结果转map
     */
    private List<Map<String, Object>> setSearchResponse(SearchResponse searchResponse) {
        ArrayList<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            list.add(sourceAsMap);
        }
        return list;
    }


    /**
     * map转对象
     */
    public static <T> T MapToBean(Map<String, ?> map, Class<T> clazz) {
        try {
            T bean = clazz.newInstance();
            BeanUtils.populate(bean, map);
            return bean;
        } catch (Exception e) {
            log.error("map cast object exception");
        }
        return null;
    }

}
